package ws.performancetest.tests.synchronization.lock;

import java.util.concurrent.Semaphore;

/**
 * Implementation of the shared memory using semaphores.
 *
 * @author Wilson Santos, 36228 <wilson.santos@ua.pt>
 */
public class SemImpl extends AbstractSharedListImpl {

  /** Semaphore used to ensure mutual exclusion. */
  private final Semaphore mutex = new Semaphore(1);
  /** Semaphore used to make sure there's room for a new element on the list. */
  private final Semaphore semAdd;
  /** Semaphore used to make sure there's at least one element on the list. */
  private final Semaphore semRemove;

  /**
   * Create a new instance.
   *
   * @param n Size of the shared list
   * @param max Shared list maximum size.
   */
  public SemImpl() {
    super();
    semAdd = new Semaphore(MAX_SIZE);
    semRemove = new Semaphore(MAX_SIZE);

    /*
     * Remove the initial list size from the number of available permits.
     * If the list has initial size "n", this means that there are:
     *  MAX_SIZE - n, permits available for element insertion.
     *  n, permits available for element removal.
     */
    semAdd.acquireUninterruptibly(list);
    semRemove.acquireUninterruptibly(MAX_SIZE - list);
  }

  public void add() throws InterruptedException {
    assert invariant();

    /* If there's no space, wait. */
    semAdd.acquire();
    try {
      mutex.acquire();
    } catch (InterruptedException ex) {
      semAdd.release();
      throw ex;
    }
    /* Add one element. */
    this.list++;

    mutex.release();
    semRemove.release();

    assert invariant();
  }

  public void remove() throws InterruptedException {
    assert invariant();

    /* If there's no elements, wait. */
    semRemove.acquire();
    try {
      mutex.acquire();
    } catch (InterruptedException ex) {
      semRemove.release();
      throw ex;
    }
    /* Remove one element. */
    this.list--;

    mutex.release();
    semAdd.release();

    assert invariant();
  }

  public void read() throws InterruptedException {
    mutex.acquire();
    mutex.release();
  }

  public void write() throws InterruptedException {
    mutex.acquire();
    mutex.release();
  }
}
